package client

import (
	"context"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	"github.com/docker/docker/api/types/swarm"
	proto "gykjgit.dccnet.com.cn/chain/proto/swarm"
	"io/ioutil"
	"strconv"
	"strings"
	"time"
)

type Service struct {
}

func newService() *Service {
	return &Service{}
}

// List 获取服务信息
func (s *Service) Create(ctx context.Context, service swarm.ServiceSpec) (types.ServiceCreateResponse, error) {
	return ObtainClient().ServiceCreate(ctx, service, types.ServiceCreateOptions{})
}

// Logs 获取服务日志
func (s *Service) Logs(ctx context.Context, serviceID string, since, until time.Time) ([]byte, error) {
	body, err := ObtainClient().ServiceLogs(ctx, serviceID, types.ContainerLogsOptions{
		ShowStdout: true,
		ShowStderr: true,
		Timestamps: false,                               // 显示时间戳
		Details:    false,                               // 显示提供给日志的额外细节
		Follow:     false,                               // 跟踪日志输出
		Tail:       "all",                               // 从日志末尾显示的行数(默认为“all”)
		Since:      strconv.FormatInt(since.Unix(), 10), // "1589772560.000000001"
		Until:      strconv.FormatInt(until.Unix(), 10), // "1589772720.000000001"
	})
	if nil != err {
		return nil, err
	}
	defer func() { _ = body.Close() }()
	return ioutil.ReadAll(body)
}

// List 获取服务信息
func (s *Service) List(ctx context.Context) ([]swarm.Service, error) {
	return ObtainClient().ServiceList(ctx, types.ServiceListOptions{})
}

// Process List the tasks of one or more services
func (s *Service) Process(ctx context.Context, filters filters.Args) ([]swarm.Task, error) {
	return ObtainClient().TaskList(ctx, types.TaskListOptions{Filters: filters})
}

// Remove Remove one or more services
func (s *Service) Remove(ctx context.Context, serviceID string) error {
	return ObtainClient().ServiceRemove(ctx, serviceID)
}

// Update Update a service
func (s *Service) Update(ctx context.Context, serviceID string, service swarm.ServiceSpec) (types.ServiceUpdateResponse, error) {
	serviceInfo, _, err := s.Inspect(ctx, serviceID)
	if nil != err {
		return types.ServiceUpdateResponse{}, err
	}
	return ObtainClient().ServiceUpdate(ctx, serviceID, serviceInfo.Version, service, types.ServiceUpdateOptions{})
}

// Inspect 获取服务信息
func (s *Service) Inspect(ctx context.Context, serviceID string) (swarm.Service, []byte, error) {
	return ObtainClient().ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{InsertDefaults: true})
}

// Resources 所有服务资源情况
func (s *Service) Resources(ctx context.Context, serviceID string) (*proto.Resources, error) {
	var (
		limit                         = &proto.Resource{}
		reservations                  = &proto.Resource{}
		limitNanoCPUs           int64 = 0
		limitMemoryBytes        int64 = 0
		reservationsNanoCPUs    int64 = 0
		reservationsMemoryBytes int64 = 0
	)
	services, err := s.List(ctx)
	if nil != err {
		return nil, err
	}
	for _, service := range services {
		if strings.Contains(service.ID, serviceID) {
			var replicas int64
			replicasStr := strconv.FormatUint(*service.Spec.Mode.Replicated.Replicas, 10)
			if replicas, err = strconv.ParseInt(replicasStr, 10, 64); nil != err {
				return nil, err
			}
			limitNanoCPUs += service.Spec.TaskTemplate.Resources.Limits.NanoCPUs * replicas
			limitMemoryBytes += service.Spec.TaskTemplate.Resources.Limits.MemoryBytes * replicas
			reservationsNanoCPUs += service.Spec.TaskTemplate.Resources.Reservations.NanoCPUs * replicas
			reservationsMemoryBytes += service.Spec.TaskTemplate.Resources.Reservations.MemoryBytes * replicas
			break
		}
	}
	limit.Cpus = strconv.FormatFloat(float64(limitNanoCPUs)/1000000000, 'f', -1, 64)
	limit.Memory = strconv.FormatFloat(float64(limitMemoryBytes)/1048576, 'f', -1, 64)
	reservations.Cpus = strconv.FormatFloat(float64(reservationsNanoCPUs)/1000000000, 'f', -1, 64)
	reservations.Memory = strconv.FormatFloat(float64(reservationsMemoryBytes)/1048576, 'f', -1, 64)
	return &proto.Resources{Limits: limit, Reservations: reservations}, nil
}

// ResourcesAll 所有服务资源情况
func (s *Service) ResourcesAll(ctx context.Context) (*proto.Resources, error) {
	var (
		limit                         = &proto.Resource{}
		reservations                  = &proto.Resource{}
		limitNanoCPUs           int64 = 0
		limitMemoryBytes        int64 = 0
		reservationsNanoCPUs    int64 = 0
		reservationsMemoryBytes int64 = 0
	)
	services, err := s.List(ctx)
	if nil != err {
		return nil, err
	}
	for _, service := range services {
		var replicas int64
		replicasStr := strconv.FormatUint(*service.Spec.Mode.Replicated.Replicas, 10)
		if replicas, err = strconv.ParseInt(replicasStr, 10, 64); nil != err {
			return nil, err
		}
		limitNanoCPUs += service.Spec.TaskTemplate.Resources.Limits.NanoCPUs * replicas
		limitMemoryBytes += service.Spec.TaskTemplate.Resources.Limits.MemoryBytes * replicas
		reservationsNanoCPUs += service.Spec.TaskTemplate.Resources.Reservations.NanoCPUs * replicas
		reservationsMemoryBytes += service.Spec.TaskTemplate.Resources.Reservations.MemoryBytes * replicas
	}
	limit.Cpus = strconv.FormatFloat(float64(limitNanoCPUs)/1000000000, 'f', -1, 64)
	limit.Memory = strconv.FormatFloat(float64(limitMemoryBytes)/1048576, 'f', -1, 64)
	reservations.Cpus = strconv.FormatFloat(float64(reservationsNanoCPUs)/1000000000, 'f', -1, 64)
	reservations.Memory = strconv.FormatFloat(float64(reservationsMemoryBytes)/1048576, 'f', -1, 64)
	return &proto.Resources{Limits: limit, Reservations: reservations}, nil
}
